#ifndef MAINWINDOW_H_
#define MAINWINDOW_H_

#include <gtkmm.h>

#include "conf/node.h"
#include "conf/attribute.h"
#include "yellowpage.h"
#include "channellist.h"
#include "widgets.h"
#include "dialog.h"
#include "notify.h"
#include "panel.h"

namespace gpyp {

class Application;
class FilterBar;
typedef boost::shared_ptr<FilterBar> RefFilterBar;
class ProcessViewElement;
typedef boost::shared_ptr<ProcessViewElement> RefProcessViewElement;
class ProcessView;
class PCRelayViewElement;
typedef boost::shared_ptr<PCRelayViewElement> RefPCRelayViewElement;
class PCRelayView;
class PanelElement;
typedef boost::shared_ptr<PanelElement> RefPanelElement;
class Panel;
class StatusBar;
typedef boost::shared_ptr<StatusBar> RefStatusBar;

namespace pref {
class Dialog;
}

class MainWindow;

class MenuBar : public Gtk::MenuBar {
private:
  YPMenu        yp_;
  ChannelMenu   ch_;
  ToolMenu      tool_;
  ViewMenu      view_;
  SettingsMenu  settings_;
  HelpMenu      help_;

protected:
  void add_menu(const Glib::ustring &label, Gtk::Menu &menu);

public:
  MenuBar(Application &app,
          MainWindow  &mainwindow,
          ChannelList &channellist);
};

class MainWindow : public conf::Element,
                   public WidgetInput<Gtk::Window> {
private:
  Application                  &app_;

  conf::RefAttributeInt         left_;
  conf::RefAttributeInt         top_;
  conf::RefAttributeInt         width_;
  conf::RefAttributeInt         height_;
  conf::RefAttributeBool        shown_;
  conf::RefAttributeBool        resizable_;

  conf::RefAttributeBool        show_menubar_;
  conf::RefAttributeBool        show_filterbar_;
  conf::RefAttributeBool        show_statusbar_;
  RefAttributePanelPos          panel_position_;
  RefPanelElement               conf_panel_;

  Gtk::Paned                   *paned_;
  RefChannelList                channellist_;
  Gtk::ScrolledWindow           sw_channellist_;
  Panel                        *panel_;

  MenuBar                       menubar_;
  RefFilterBar                  filterbar_;
  RefStatusBar                  statusbar_;
  Gtk::VBox                     vbox_;

  DialogWrapper<pref::Dialog>     pref_dialog_;
  DialogWrapper<ConfigViewDialog> config_view_dialog_;
  DialogWrapper<HelpBCDialog>     help_bc_dialog_;
  DialogWrapper<HelpOptionDialog> help_opts_dialog_;
  DialogWrapper<EnvInfoDialog>    env_info_dialog_;
  DialogWrapper<AboutDialog>      about_dialog_;

  DialogWrapper<UptestDialog>   uptest_dialog_;
  DialogWrapper<YPInfoDialog>   ypinfo_dialog_;
  DialogWrapper<ChInfoDialog>   chinfo_dialog_;

#ifndef DISABLE_STATUS_ICON
  RefStatusIcon                 status_icon_;
#endif
  RefNotify                     notify_;

  bool                          loading_count_shown_;
  sigc::connection              conn_update_loading_count_;

  BuiltinCommand               &bc_pref_;

private:
  void save_position();
  void restore_position();

protected:
  virtual void on_presave();
  void conf_on_loaded();

  virtual void on_realize();
  virtual void on_map();
  virtual void on_unmap();
  virtual void on_hide();
  virtual bool on_delete_event(GdkEventAny *event);

  virtual void prepare_commandopts(CommandOpts &opts);

  void resizable_on_changed();

  void panel_position_on_changed();

  bool init_idle();
  bool realize_idle();

#ifndef DISABLE_STATUS_ICON
  void status_icon_on_activate();
  void status_icon_enable_on_changed();
#endif

  bool update_loading_count_timeout();
  void ypcollection_on_loading_count_changed(size_t count);

public:
  inline conf::RefAttributeInt left() {
    return left_;
  }
  inline conf::RefAttributeInt top() {
    return top_;
  }
  inline conf::RefAttributeInt width() {
    return width_;
  }
  inline conf::RefAttributeInt height() {
    return height_;
  }
  inline conf::RefAttributeBool shown() {
    return shown_;
  }
  inline conf::RefAttributeBool resizable() {
    return resizable_;
  }

  inline conf::RefAttributeBool show_menubar() {
    return show_menubar_;
  }
  inline conf::RefAttributeBool show_filterbar() {
    return show_filterbar_;
  }
  inline conf::RefAttributeBool show_statusbar() {
    return show_statusbar_;
  }
  inline RefAttributePanelPos panel_position() {
    return panel_position_;
  }
  inline RefPanelElement panel() {
    return conf_panel_;
  }

  inline RefFilterBar filterbar() {
    return filterbar_;
  }
  inline RefChannelList channellist() {
    return channellist_;
  }
  inline RefStatusBar statusbar() {
    return statusbar_;
  }

#ifndef DISABLE_STATUS_ICON
  inline RefStatusIcon status_icon() {
    return status_icon_;
  }
#endif
  inline RefNotify notify() {
    return notify_;
  }

  void show();
  void hide();

  pref::Dialog &pref();

  void show_pref();
  void show_config_view();
  void show_help_bc();
  void show_help_opts();
  void show_env_info();
  void show_about();

  void uptest(RefYellowPage yp);
  void ypinfo(RefYellowPage yp);
  void chinfo(RefChannel ch);

  inline Panel *get_panel() {
    return panel_;
  }

private:
  bool bc_pref_show(ShellLine &line, CommandOpts &opts);

  bool bc_uptest(ShellLine &line, CommandOpts &opts);

  bool bc_yp_viewinfo(ShellLine &line, CommandOpts &opts);
  bool bc_ch_rematch_favorite(ShellLine &line, CommandOpts &opts);
  bool bc_ch_viewinfo(ShellLine &line, CommandOpts &opts);

public:
  MainWindow(Application &app);
  virtual ~MainWindow();
};
typedef boost::shared_ptr<MainWindow> RefMainWindow;

class FilterBar : public conf::Element,
                  public Gtk::VBox {
private:
  class Selector;
  class ModelSelector : public Gtk::ComboBox {
  private:
    Application &app_;

    Gtk::TreeModelColumnRecord          columns_;
    Gtk::TreeModelColumn<Glib::ustring> column_name_;
    Gtk::TreeModelColumn<CLModel>       column_model_;
    Glib::RefPtr<Gtk::ListStore>        liststore_;

    void append(const Glib::ustring &name, CLModel model);

    void model_on_changed();
    void main_on_run();

  protected:
    virtual void on_changed();

  public:
    ModelSelector(Application &app);
  };

  class Filter : public Gtk::ToggleButton {
  private:
    sigc::signal<void, GdkEventButton *> signal_clicked_;
  public:
    inline sigc::signal<void, GdkEventButton *> signal_clicked() {
      return signal_clicked_;
    }

  private:
    Application &app_;
    RefFavorite  favorite_;
    bool         visible_;

    Gtk::Label   label_;
    Gtk::Entry   entry_;
    Gtk::HBox    box_;

  protected:
    void update_visible();

    virtual bool on_button_press_event(GdkEventButton* event);

    bool entry_on_key_press_event(GdkEventKey *event);
    void entry_on_activate();
#ifdef GTK_ENTRY_HAS_ICON
    void entry_on_icon_press(Gtk::EntryIconPosition pos,
                             const GdkEventButton  *event);
#endif

  public:
    inline RefFavorite get_favorite() {
      return favorite_;
    }

    bool get_enable();
    void set_visible(bool visible);

    void start_rename();

    Filter(Application &app, RefFavorite favorite);
  };
  class FilterMenu : public Gtk::Menu {
  private:
    Application          &app_;
    Gtk::Window          &parent_;
    Filter               *filter_;

    Gtk::CheckMenuItem    mi_filter_;

    Gtk::MenuItem         mi_bgcolor_;
    Gtk::MenuItem         mi_fgcolor_;
    Gtk::MenuItem         mi_sort_;
    ChSortMenu            menu_sort_;

    SimpleMenuItem        mi_rename_;
    SimpleMenuItem        mi_setting_;
    SimpleMenuItem        mi_remove_;

    void refresh_color();
    void refresh_sort();

    void filter_on_toggled();
    void bgcolor_on_selected(const Color &color);
    void fgcolor_on_selected(const Color &color);
    void sort_on_changed(ChSort sort);
    void remove_on_activate();
    void rename_on_activate();
    void setting_on_activate();

  public:
    void set(Filter &filter);

    FilterMenu(Application &app, Gtk::Window &parent);
  };
  class Selector : public Gtk::HBox {
  private:
    class Scroller : public Gtk::HBox {
    private:
      Selector   &selector_;

      Gtk::Arrow  a_left_;
      Gtk::Button b_left_;
      Gtk::Arrow  a_right_;
      Gtk::Button b_right_;

      void left_on_clicked();
      void right_on_clicked();

    public:
      inline void set_left_scrollable(bool scrollable) {
        b_left_.set_sensitive(scrollable);
      }
      inline void set_right_scrollable(bool scrollable) {
        b_right_.set_sensitive(scrollable);
      }

      Scroller(Selector &selector);
    };

private:
    Application            &app_;

    typedef boost::ptr_list<Filter> FilterList;
    RefFavoriteCollection   favorites_;
    Gtk::HBox               left_box_;
    Gtk::Button             button_all_;
    FilterList              filters_;
    Scroller                scroller_;
    Gtk::HBox               right_box_;
    ModelSelector           model_selector_;

    size_t                  show_start_;
    int                     last_width_;

    FilterMenu              filter_menu_;

    bool                    stop_refilter_;

    BuiltinCommand         &bc_filterbar_;

  protected:
    virtual void on_size_request(Gtk::Requisition *requisition);
    virtual void on_size_allocate(Gtk::Allocation &allocation);

    void update_size(const Gtk::Allocation &allocation, bool force = false);

    void refilter();

    Filter *add(RefFavorite favorite);

    void on_add_item(RefFavorite favorite);
    void on_remove_item(RefFavorite favorite);
    void on_reorder_item(RefFavorite favorite,
                         RefFavorite prev,
                         RefFavorite next);
    void on_clear();

    void filter_on_clicked(GdkEventButton *event, Filter *filter);
    virtual bool on_scroll_event(GdkEventScroll *event);

  public:
    void toggle();

    bool match(RefChannel ch);

    void select(RefFavorite favorite);
    void unselect_all();

    void scroll_left();
    void scroll_right();

  private:
    bool bc_filterbar_show(ShellLine &line, CommandOpts &opts);
    bool bc_filterbar_hide(ShellLine &line, CommandOpts &opts);
    bool bc_filterbar_toggle(ShellLine &line, CommandOpts &opts);

  public:
    Selector(Application &app, Gtk::Window &parent);
  };

  class SearchBar : public SearchBox {
  private:
    Application &app_;

  protected:
    virtual void prepare_commandopts(CommandOpts &opts);
    virtual bool on_key_press_event(GdkEventKey *event);

    virtual void on_hide();

  public:
    SearchBar(Application &app, RefSearch search);
  };

public:
  static Rule::Target SEARCH_TARGETS;

private:

  Application &app_;
  RefSearch    search_;

  Selector     filter_selector_;
  SearchBar    searchbar_;

  void main_on_run();

  bool filter_func(RefChannel ch);

  void search_on_changed();

  void searchbar_on_populate_popup(Gtk::Menu *menu);

public:
  inline Selector &filterbar() {
    return filter_selector_;
  }
  inline SearchBar &searchbar() {
    return searchbar_;
  }

  void create_filter();

  FilterBar(Application &app, Gtk::Window &parent);
};

typedef std::vector<const Channel::Property *> ChPropList;
class AttributeChPropList :
    public conf::Attribute<ChPropList, AttributeChPropList> {
public:
  inline static ChPropList to_value(const Glib::ustring &str) {
    ChPropList res;
    Glib::ustring::size_type pos = 0, next;
    while(true) {
      next = str.find(',', pos);
      Glib::ustring term(str, pos,
                         next == Glib::ustring::npos ? next : next - pos);
      res.push_back(Channel::get_property(term));
      if (next == Glib::ustring::npos) break;
      pos = next + 1;
    }
    return res;
  }
  inline static Glib::ustring to_string(const ChPropList &value) {
    Glib::ustring res;
    for(ChPropList::const_iterator iter = value.begin();
        iter != value.end(); ++iter) {
      if (!res.empty()) res += ",";
      res += (*iter)->name;
    }
    return res;
  }

  inline AttributeChPropList(const Glib::ustring &name,
                             const ChPropList &defval) :
    conf::Attribute<ChPropList, AttributeChPropList>(name, defval)
  {
  }
};

class RefAttributeChPropList :
    public conf::RefAttribute<AttributeChPropList> {
private:
  inline static ChPropList create_list(const Channel::Property **props) {
    ChPropList res;
    for(const Channel::Property **prop = props; *prop; ++prop) {
      res.push_back(*prop);
    }
    return res;
  }

public:
  const ChPropList &operator*() const {
    return get()->get_value();
  }
  const ChPropList &operator=(const ChPropList &value) {
    get()->set_value(value);
    return value;
  }
  ChPropList &operator=(ChPropList &value) {
    get()->set_value(value);
    return value;
  }

  inline RefAttributeChPropList(const Glib::ustring      &name,
                                const Channel::Property **props) :
    conf::RefAttribute<AttributeChPropList>(name, create_list(props))
  {
  }
};

class ChannelViewElement : public panel::PageElement {
private:
  static const Channel::Property *DEFAULT_PROPS[];

private:
  RefAttributeChPropList show_props_;

public:
  inline RefAttributeChPropList show_props() {
    return show_props_;
  }

  inline ChannelViewElement(const Glib::ustring &name, bool enable) :
    panel::PageElement(name, enable),
    show_props_       ("show_props", DEFAULT_PROPS)
  {
    add(show_props_);
  }
};
typedef boost::shared_ptr<ChannelViewElement> RefChannelViewElement;

class ChannelView : public panel::Page<ChPropertyView> {
private:
  class PropMenuItem : public Gtk::CheckMenuItem {
  private:
    RefAttributeChPropList   show_props_;
    const Channel::Property *prop_;

  protected:
    virtual void on_toggled();

  public:
    PropMenuItem(RefAttributeChPropList   show_props,
                 const Channel::Property *prop);
  };

private:
  RefChannelViewElement conf_;

  sigc::connection conn_conf_show_props_changed_;

protected:
  virtual Gdk::Rectangle *on_show_property(Glib::RefPtr<Gtk::TextBuffer> buf,
                                           RefChannel                    ch,
                                           const Channel::Property      *prop,
                                           Gdk::Rectangle               &rect);
  virtual void on_populate_popup(Gtk::Menu *menu);

public:
  ChannelView(Application &app, RefChannelViewElement conf);
  virtual ~ChannelView();
};

class LogViewElement : public panel::PageElement {
private:
  conf::RefAttributeBool show_debug_;

public:
  inline conf::RefAttributeBool show_debug() {
    return show_debug_;
  }

  inline LogViewElement(const Glib::ustring &name, bool enable) :
    panel::PageElement(name, enable),
    show_debug_       ("show_debug", false)
  {
    add(show_debug_);
  }
};
typedef boost::shared_ptr<LogViewElement> RefLogViewElement;

class LogView : public panel::Page<Gtk::VBox> {
public:
  static const int MAX_LINES;

private:
  Application                &app_;
  RefLogViewElement           conf_;

  bool                        connected_;

  TextView                    view_;
  Gtk::ScrolledWindow         sw_view_;
  Entry                       command_entry_;

  Glib::RefPtr<Gtk::TextTag>  tag_log_;
  Glib::RefPtr<Gtk::TextTag>  tag_err_;
  Glib::RefPtr<Gtk::TextTag>  tag_dbg_;

  Gtk::Adjustment            *vadjustment_;
  sigc::connection            conn_vadjustment_changed_;

  int                         erase_count_;
  Glib::RefPtr<Gtk::TextMark> erase_mark_;

  BuiltinCommand             &bc_logview_;

protected:
  void view_on_populate_popup(Gtk::Menu *menu);

  void log_on_message(log::Logger::id_type id, const Glib::ustring &message);

  void vadjustment_on_changed();
  void view_on_set_scroll_adjustments(Gtk::Adjustment *hadjustment,
                                      Gtk::Adjustment *vadjustment);

private:
  void command_entry_on_activate();

  bool bc_clear(ShellLine &line, CommandOpts &opts);

public:
  LogView(Application &app, RefLogViewElement conf);
  virtual ~LogView();
};

class PanelElement : public panel::PanelElement {
private:
  RefProcessViewElement processes_;
  RefPCRelayViewElement peercast_;
  RefChannelViewElement channel_;
  RefLogViewElement     log_;

public:
  inline RefProcessViewElement processes() {
    return processes_;
  }
  inline RefPCRelayViewElement peercast() {
    return peercast_;
  }
  inline RefChannelViewElement channel() {
    return channel_;
  }
  inline RefLogViewElement log() {
    return log_;
  }

  PanelElement(const Glib::ustring &name);
};
typedef boost::shared_ptr<PanelElement> RefPanelElement;

class Panel : public panel::Panel {
private:
  Application    &app_;
  RefPanelElement conf_;

  ProcessView *page_processes_;
  PCRelayView *page_peercast_;
  ChannelView *page_channel_;
  LogView     *page_log_;

private:
  panel::PageBase *page_create_processes();
  panel::PageBase *page_create_peercast();
  panel::PageBase *page_create_channel();
  panel::PageBase *page_create_log();

public:
  inline ProcessView *page_processes() {
    return page_processes_;
  }
  inline PCRelayView *page_peercast() {
    return page_peercast_;
  }
  inline ChannelView *page_channel() {
    return page_channel_;
  }
  inline LogView *page_log() {
    return page_log_;
  }

  Panel(Application         &app,
        RefPanelElement      conf,
        RefAttributePanelPos position,
        Gtk::Paned          &paned);
};

class StatusBar : public conf::Element,
                  public EntryLabel {
private:
  Application       &app_;

  RefAttributeFormat format_;
  bool               hold_update_;

  bool               in_popup_;
  sigc::connection   conn_popup_deactivate_;

  void popup_on_deactivate();
  virtual void on_populate_popup(Gtk::Menu *menu);

  void on_channel_selected(RefChannel ch);
  void update_text();

  virtual bool on_focus_out_event(GdkEventFocus *event);

public:
  StatusBar(Application &app);
};

}/*gpyp*/

#endif/*MAINWINDOW_H_*/
